/*
 * Copyright (C) 2019-2020 Alibaba Group Holding Limited
 */

#include "app_main.h"
#include "app_lpm.h"
#include <http_client.h>
#include <yoc/record.h>
#include "silan_codec_regs.h"
#include "silan_syscfg_regs.h"
#include "breeze_export.h"

void http_download(const char *url, const char *save_dir)
{
    int   http_response_size = 4 * 1024;
    char *buffer             = aos_malloc_check(http_response_size + 1);
    int   rec_fd             = 0;
    char  save_path[128];
    int   content_length = 0, total_read_len = 0, read_len = 0;

    if (!save_dir || strlen(save_dir) < 2) {
        printf("save path err\n");
        return;
    }

    if (!wifi_internet_is_connected()) {
        printf("network not connected!\n");
        return;
    }

    if (save_dir[strlen(save_dir) - 1] == '/') {
        snprintf(save_path, 128, "%s%s", save_dir, strrchr(url, '/') + 1);
    } else {
        snprintf(save_path, 128, "%s/%s", save_dir, strrchr(url, '/') + 1);
    }

    rec_fd = aos_open(save_path, O_WRONLY | O_CREAT | O_TRUNC);
    if (rec_fd < 0) {
        printf("Create file %s error\n", save_path);
        return;
    }

    http_client_config_t config = {
        .url = url,
        //.event_handler = _http_event_handler,
        .timeout_ms  = 10000,
        .buffer_size = 4 * 1024,
        // .cert_pem = ca_cert,
    };

    http_client_handle_t client = http_client_init(&config);
    if (client == NULL) {
        printf("http request init failed, url \"%s\"\n", url);
        return;
    }

    http_errors_t err;
    if ((err = http_client_open(client, 0)) != HTTP_CLI_OK) {
        printf("Failed to open HTTP connection: 0x%x\n", err);
        goto http_err;
    }

    content_length = http_client_fetch_headers(client);
    while (total_read_len < content_length) {
        read_len = http_client_read(client, buffer, http_response_size);
        if (read_len <= 0) {
            printf("Error read data\n");
            goto http_err;
        }

        int wsize = aos_write(rec_fd, buffer, read_len);
        if (wsize != read_len) {
            printf("write sd failed %d %d\n", wsize, read_len);
            goto http_err;
        }

        total_read_len += read_len;
    }
    printf("download finished, save to %s!\n", save_path);

http_err:
    http_client_cleanup(client);
    aos_free(buffer);
    aos_close(rec_fd);
}

#if defined(CONFIG_CLOCK_ALARM) && CONFIG_CLOCK_ALARM
static void clock_print_help(void)
{
    printf("Usage:\n\
           - app clock del <clock_id>: delete clock <id>\n\
           - app clock set <clock_id> <period> <hour> <minute> <second>: create a new alarm\n\
           - app clock get: get all set alarms\n");
}

static void rtc_print_help(void)
{
    printf("Usage:\n\
            - app rtc get: get rtc time\n\
            - app rtc update: update system time to rtc\n\
            - app rtc set <day> <month> <hour> <min>: set rtc time\n");
}
#endif

#define CHUNK_SIZE (5 * 3200)
static int ws_file_send(const char *ip, const char *port, const char *file_path)
{
    char        buf[64];
    char        buf2[128];
    struct stat fstat;
    char        ws_uri[128];
    uint32_t    total_size;
    int         ret, success = 0;
    rec_hdl_t   rec_hdl = NULL;

    ret = aos_stat(file_path, &fstat);
    if (ret < 0) {
        printf("access file %s error\n", file_path);
        return -1;
    }

    total_size = fstat.st_size;

    snprintf(ws_uri, 128, "ws://%s:%s", ip, port);
    snprintf(buf, sizeof(buf), "sdbuffer://handle=%s", file_path);
    snprintf(buf2, sizeof(buf2), "%s/%s", ws_uri, strrchr(file_path, '/') + 1);

    rec_hdl = record_register(buf, buf2);
    if (!rec_hdl) {
        printf("ws register failed\n");
        goto END;
    }

    // record_set_data_ready_cb(rec_hdl, data_ready, NULL);
    record_set_chunk_size(rec_hdl, CHUNK_SIZE);
    ret = record_start(rec_hdl);
    if (ret == 0) {
        int read_bytes, sent_bytes;
        int last_send_bytes = 0;
        int stuck_cnt       = 0;

        while (1) {
            record_get_count(rec_hdl, &read_bytes, &sent_bytes);
            if (total_size - CHUNK_SIZE < (uint32_t)read_bytes) {
                aos_msleep(500);
                success = 1;
                break;
            }

            if (last_send_bytes == sent_bytes) {
                if (++stuck_cnt > 50) {
                    printf("send timeout!\n");
                    break;
                }
            } else {
                stuck_cnt = 0;
            }

            last_send_bytes = sent_bytes;
            aos_msleep(100);
        }

        ret = record_stop(rec_hdl);
    }

END:
    record_unregister(rec_hdl);

    if (success) {
        printf("file %s sent success\n", file_path);
    }
    return 0;
}

static void cmd_app_func(char *wbuf, int wbuf_len, int argc, char **argv)
{
    int ret = 0;

    if (strcmp(argv[1], "led") == 0) {
        if (strcmp(argv[2], "off") == 0) {
            app_set_led_enabled(0);
            app_set_led_state(LED_TURN_OFF);
        } else {
            app_set_led_enabled(1);

            int stat = atoi(argv[2]);
            app_set_led_state(stat);
        }
    }
#ifdef AW2013_ENABLED
    else if (strcmp(argv[1], "led_rgb") == 0) {
        if (argc != 5) {
            printf("app led_rgb <led_id> <mode> <dime>");
        }

        aw2013_led_control(g_i2c_dev, atoi(argv[2]), atoi(argv[3]), atoi(argv[4]));
    }
#endif
#ifdef AW9523_ENABLED
    else if (strcmp(argv[1], "led_array") == 0) {
        if (argc != 4) {
            printf("app led_array <led_id> <dime>");
        }

        if (strcmp(argv[2], "all") == 0) {
            for (int i = LED_ARRAY_1; i <= LED_ARRAY_12; ++i) {
                aw9523_led_control(g_i2c_dev, i, atoi(argv[3]));
            }
        } else {
            aw9523_led_control(g_i2c_dev, atoi(argv[2]), atoi(argv[3]));
        }
    } else if (strcmp(argv[1], "aw9523_input") == 0) {
        uint16_t val;
        aw9523_read_input(g_i2c_dev, &val, atoi(argv[2]));
        printf("aw9523 input: %x\n", val);
    }
#endif
    else if (strcmp(argv[1], "lpm") == 0) {
        if (argc < 3) {
            printf("Usages:\n\
                    - app lpm 1 [timeout]: enter low power mode, if timeout is set, \n\
                                           auto wake up after timeout(ms)\n\
                    - app lpm 2          : enter deep sleep mode\n");
            return;
        }
        int policy = atoi(argv[2]);
        if (policy) {
            if (policy == LPM_POLICY_LOW_POWER || policy == LPM_POLICY_DEEP_SLEEP) {
                if (4 == argc) {
                    int timeout = atoi(argv[3]);
                    lpm_set_timeout(timeout);
                }
                app_lpm_sleep(policy, 0);
            } else {
                printf("lpm policy (%d) set error\n", atoi(argv[2]));
                return;
            }
        }
    } else if (strcmp(argv[1], "power_off") == 0) {
        app_lpm_sleep(LPM_POLICY_DEEP_SLEEP, 1);
    } else if (strcmp(argv[1], "wifips") == 0) {
        wifi_set_lpm(atoi(argv[2]));
    } else if (strcmp(argv[1], "mac") == 0) {
        uint8_t mac[6];
        wifi_getmac(mac);
        printf("%02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    } else if (strcmp(argv[1], "pa") == 0) {
        int mute = atoi(argv[2]);
        app_speaker_mute(mute);
        printf("set pa mute %d\n", mute);
    } else if (strcmp(argv[1], "ldo") == 0) {
        if (argc == 3) {
            int ldo = atoi(argv[2]);

            extern void silan_dsp_ldo_disable();
            extern void silan_dsp_ldo_config(int level);
            extern void silan_soc_ldo_config(int level);
            switch (ldo) {
            case 0:
                printf("ldo disabled\n");
                silan_dsp_ldo_disable();
                break;
            case 1:
                printf("ldo 1.2v\n");
                silan_dsp_ldo_config(0 /* LDO_OUT_12V */);
                silan_soc_ldo_config(0);
                break;
            case 2:
                printf("ldo 1.0v\n");
                silan_dsp_ldo_config(1 /* LDO_OUT_10V */);
                silan_soc_ldo_config(1);
                break;
            case 3:
                printf("ldo 0.8v\n");
                silan_dsp_ldo_config(2 /* LDO_OUT_08V */);
                silan_soc_ldo_config(2);
                break;
            default:;
            }
        }
#if defined(CONFIG_BATTERY_ADC) && CONFIG_BATTERY_ADC
    } else if (strcmp(argv[1], "battery") == 0) {
        printf("battery: %d\n", app_battery_read());
#endif
#if defined(CONFIG_CLOCK_ALARM) && CONFIG_CLOCK_ALARM
    } else if (strcmp(argv[1], "rtc") == 0) {
        if (argc > 2) {
            if (strcmp(argv[2], "update") == 0) {
                rtc_from_system();
            } else if (strcmp(argv[2], "get") == 0) {
                rtc_to_system();
            } else if (strcmp(argv[2], "debug") == 0) {
                extern void rtc_debug(void);
                rtc_debug();
            } else if (strcmp(argv[2], "set") == 0) {
                if (argc >= 7) {
                    struct tm tm_set;
                    tm_set.tm_wday = atoi(argv[3]);
                    tm_set.tm_mday = atoi(argv[4]);
                    tm_set.tm_hour = atoi(argv[5]);
                    tm_set.tm_min  = atoi(argv[6]);
                    tm_set.tm_sec  = 0;
                    rtc_set_alarm(&tm_set);
                } else {
                    rtc_print_help();
                }
            }
        } else {
            rtc_print_help();
        }
    } else if (strcmp(argv[1], "clock") == 0) {
        if (argc >= 3) {
            int id = 0;
            if (strcmp(argv[2], "del") == 0) {
                if (4 == argc) {
                    id  = atoi(argv[3]);
                    ret = clock_alarm_set(id, NULL);
                    printf("clock alarm: clock id %d is delete. ret %d\n", id, ret);
                    return;
                }
            } else if (strcmp(argv[2], "set") == 0) {
                if (8 == argc) {
                    id = atoi(argv[3]);
                    clock_alarm_config_t cli_time;
                    cli_time.period = atoi(argv[4]);
                    cli_time.hour   = atoi(argv[5]);
                    cli_time.min    = atoi(argv[6]);
                    cli_time.sec    = atoi(argv[7]);

                    printf("set alarm: id %d, period %d, time %02d:%02d:%02d\n", id,
                           cli_time.period, cli_time.hour, cli_time.min, cli_time.sec);
                    id = clock_alarm_set(id, &cli_time);
                    if (id == -2) {
                        printf("clock alarm has repeat time.\n");
                    } else if (id == -1) {
                        printf("clock alarm has full.\n");
                    }
                    if (id == -4) {
                        printf("clock alarm time config error.\n");
                    } else {
                        clock_alarm_enable(id, 1);
                        printf("clock id %d set success. time %02d:%02d:%02d, period %d.\n", id,
                               cli_time.hour, cli_time.min, cli_time.sec, cli_time.period);
                    }
                    return;
                }
            } else if (strcmp(argv[2], "get") == 0) {
                clock_alarm_ctx_t *clock_info;
                struct tm *        clock_tm;
                uint8_t            has_clock = 0;
                for (uint8_t idx = 1; idx < CLOCK_ALARM_NUM + 1; idx++) {
                    clock_info = NULL;
                    clock_info = clock_alarm_get(idx);
                    if (NULL != clock_info) {
                        clock_tm = gmtime(&clock_info->time);
                        printf("clock id[%d], time[%02d:%02d:%02d], period[%d], enable[%d]\n",
                               clock_info->id, clock_tm->tm_hour + TIME_ZONE, clock_tm->tm_min,
                               clock_tm->tm_sec, clock_info->period, clock_info->enable);
                        has_clock = 1;
                    }
                }
                if (0 == has_clock) {
                    printf("no clock alarm set!\n");
                }
                return;
            }
        }
        /* command is not recognized */
        clock_print_help();
        return;
#endif
    } else if (strcmp(argv[1], "wifi_prov") == 0) {
        extern int wifi_prov_method;
        if (strcmp(argv[2], "softap") == 0) {
            wifi_prov_method = WIFI_PROVISION_SOFTAP;
            printf("wifi provision method switch to softap!\n");
        } else if (strcmp(argv[2], "dev_ap") == 0) {
            wifi_prov_method = WIFI_PROVISION_SL_DEV_AP;
            printf("wifi provision method switch to smartliving device ap!\n");
        } else if (strcmp(argv[2], "smartconfig") == 0) {
            wifi_prov_method = WIFI_PROVISION_SL_SMARTCONFIG;
            printf("wifi provision method switch to smartliving smartconfig!\n");
        } else if (strcmp(argv[2], "ble") == 0) {
            breeze_disconnect_ble();
            breeze_stop_advertising();
            wifi_prov_method = WIFI_PROVISION_SL_BLE;
            aos_kv_del("AUTH_AC_AS");
            aos_kv_del("AUTH_KEY_PAIRS");
        } else {
            printf("app wifi_prov softap/dev_ap/smartconfig/ble");
            return;
        }

        aos_kv_setint("wprov_method", wifi_prov_method);
        app_sys_set_boot_reason(BOOT_REASON_WIFI_CONFIG);
        aos_reboot();
    } else if (strcmp(argv[1], "http_download") == 0) {
        if (argc == 4) {
            http_download(argv[3], argv[2]);
        } else {
            printf("app http_download <save directory> <url>\n");
        }
    } else if (strcmp(argv[1], "ws_file_send") == 0) {
        if (argc == 5) {
            ws_file_send(argv[2], argv[3], argv[4]);
        } else {
            printf("app ws_file_send <ip> <port> <file path>\n");
        }
    } else if (strcmp(argv[1], "gain") == 0) {
        if (strcmp(argv[2], "mic") == 0) {
            if (strcmp(argv[3], "read") == 0) {
                if (argc == 4) {
                    uint8_t tmp_R6_h = CODEC2_AD_R6 & 0xc0;
                    uint8_t tmp_R7_h = CODEC2_AD_R7 & 0xc0;
                    uint8_t tmp_R6   = CODEC2_AD_R6 & 0x3f;
                    uint8_t tmp_R7   = CODEC2_AD_R7 & 0x3f;
                    printf("mic_boost_l=%d, mic_boost_r=%d, mic_analog_l=%d, mic_analog_r=%d\n",
                           (tmp_R6_h >> 6), (tmp_R7_h >> 6), tmp_R6 / 2, tmp_R7 / 2);
                } else {
                    printf("(error): app gain mic read\n");
                }
            } else if (strcmp(argv[3], "set_boost") == 0) {
                if (argc == 5) {
                    int set_adc2_left_channel_micboost_gain(int);
                    int set_adc2_right_channel_micboost_gain(int);
                    set_adc2_left_channel_micboost_gain(atoi(argv[4]));
                    set_adc2_right_channel_micboost_gain(atoi(argv[4]));
                    printf("mic_boost_gain(0=0dB 1=6dB 2=12dB 3=20dB) %d\n", atoi(argv[4]));
                } else {
                    printf("(error): app gain mic set_boost <0/1/2/3>\n\tmicboost_gain: 0=0dB "
                           "1=6dB 2=12dB 3=20dB\n");
                }
            } else if (strcmp(argv[3], "set_analog") == 0) {
                if (argc == 5) {
                    int set_adc2_left_channel_mic_analog_gain(int);
                    int set_adc2_right_channel_mic_analog_gain(int);
                    set_adc2_left_channel_mic_analog_gain(atoi(argv[4]));
                    set_adc2_right_channel_mic_analog_gain(atoi(argv[4]));
                    printf("mic_analog_gain(0-63 16=0dB 0.75dB/step) %d\n", atoi(argv[4]));
                } else {
                    printf("(error): app gain mic set_analog <0-63>\n\tmic_analog_gain: 0-63 "
                           "16=0dB 0.75dB/step\n");
                }
            } else {
                printf("(error): app gain mic read/set_boost/set_analog < >\n");
            }
        } else if (strcmp(argv[2], "ref") == 0) {
            if (strcmp(argv[3], "read") == 0) {
                if (argc == 4) {
                    printf("ref_adc_l=%d, ref_dac_r=%d\n", *(&CODEC1_AD_R6), *(&CODEC1_DA_R9));
                } else {
                    printf("(error): app gain ref read\n");
                }
            } else if (strcmp(argv[3], "set_adc") == 0) {
                if (argc == 5) {
                    int set_adc1_left_channel_other_analog_gain(int);
                    set_adc1_left_channel_other_analog_gain(atoi(argv[4]));
                    printf("ref_adc_gain(0-63 16=0dB 0.75dB/step) %d\n", atoi(argv[4]));
                } else {
                    printf("(error) (gain: 0-63 16=0dB 0.75dB/step): app gain ref set_adc < >\n");
                }
            } else if (strcmp(argv[3], "set_dac") == 0) {
                if (argc == 5) {
                    int set_dac1_right_channel_analog_gain(int);
                    set_dac1_right_channel_analog_gain(atoi(argv[4]));
                    printf("ref_dac_gain(0-63 0=-25dB 49=0dB 63=38dB 1dB/step) %d\n",
                           atoi(argv[4]));
                } else {
                    printf("(error) (gain: 0-63 0=-25dB 49=0dB 63=38dB 1dB/step): app gain ref_dac "
                           "< >\n");
                }
            } else {
                printf("(error): app gain ref read/set_adc/set_dac\n");
            }
        } else if (strcmp(argv[2], "pa") == 0) {
            if (strcmp(argv[3], "read") == 0) {
                if (argc == 4) {
                    printf("pa_dac_gain=%d\n", *(&CODEC1_DA_R8));
                } else {
                    printf("(error): app gain pa read\n");
                }
            } else if (strcmp(argv[3], "set") == 0) {
                if (argc == 5) {
                    int set_dac1_left_channel_analog_gain(int);       //
                    set_dac1_left_channel_analog_gain(atoi(argv[4])); //
                    printf("ref_dac_gain(0-63 0=-25dB 49=0dB 63=38dB 1dB/step) %d\n",
                           atoi(argv[4]));
                } else {
                    printf("(error): app gain pa set <0-63>\n");
                }
            } else {
                printf("(error): app gain pa read/set\n");
            }
        } else {
            printf("(error): app gain <mic/ref/pa> <read/set>\n");
        }
    } else {
        printf("app led/lpm/pa/mic/ref/gain\n");
    }
}

void cli_reg_cmd_app(void)
{
    /* WiFi 测试CLI命令 */
    extern void                     iwpriv(char *wbuf, int wbuf_len, int argc, char **argv);
    static const struct cli_command cmd_info_wifi = {"iwpriv", "test iwpriv", iwpriv};
    aos_cli_register_command(&cmd_info_wifi);

    /* 其他CLI命令 */
    static const struct cli_command cmd_info_app = {"app", "app test cmd", cmd_app_func};
    aos_cli_register_command(&cmd_info_app);
}
